/* 手写Promise */
const PENDING = 'padding';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class MyPromise {
  status = PENDING;
  value = undefined;
  reason = undefined;
  successCallback = [];
  failCallback = [];
  constructor(executor) {
    try {
      executor(this.resolve, this.reject);
    } catch (e) {
      this.reject(e);
    }
  }
  resolve = value => {
    if (this.status !== PENDING) return;
    this.status = FULFILLED;
    this.value = value;
    while (this.successCallback.length) {
      this.successCallback.shift()();
    }
  };
  reject = reason => {
    if (this.status !== PENDING) return;
    this.status = REJECTED;
    this.reason = reason;
    while (this.failCallback.length) {
      this.failCallback.shift()();
    }
  };
  then(
    successCallback = value => value,
    failCallback = reason => {
      throw reason;
    }
  ) {
    let promiseThen = new MyPromise((resolve, reject) => {
      const commonCallback = callback => {
        setTimeout(() => {
          try {
            let thenResult = commonCallback(this.value);
            resolvePromise(promiseThen, thenResult, resolve, reject);
          } catch (e) {
            reject(e);
          }
        }, 0);
      };
      if (this.status === FULFILLED) {
        commonCallback(successCallback);
      } else if (this.status === REJECTED) {
        commonCallback(failCallback);
      } else {
        this.successCallback = [
          ...this.successCallback,
          () => {
            commonCallback(successCallback);
          },
        ];
        this.failCallback = [
          ...this.failCallback,
          () => {
            commonCallback(failCallback);
          },
        ];
      }
    });
    return promiseThen;
  }
  catch(failCallback) {
    return this.then(undefined, failCallback);
  }
  finally(callback) {
    return this.then(
      value => {
        return MyPromise.resolve(callback()).then(() => value);
      },
      reason => {
        return MyPromise.resolve(callback()).then(() => {
          throw reason;
        });
      }
    );
  }
  static all(array) {
    let result = [];
    let order = 0;
    return new MyPromise((resolve, reject) => {
      const addData = (key, value) => {
        result[key] = value;
        order++;
        if (order === array.length) {
          resolve(result);
        }
      };
      array.forEach((current, index) => {
        if (current instanceof MyPromise) {
          current.then(
            value => addData(index, value),
            reason => reject(reason)
          );
        } else {
          addData(index, current);
        }
      });
    });
  }
  static race(array) {
    return new MyPromise((resolve, reject) => {
      array.forEach(current => {
        let value = current;
        if (!(value instanceof MyPromise)) {
          value = MyPromise.resolve(value);
        }
        value.then(
          value => resolve(value),
          reason => reject(reason)
        );
      });
    });
  }
  static allSettled(array) {
    let result = [];
    let order = 0;
    return new MyPromise(resolve => {
      const addData = (key, value) => {
        result[key] = value;
        order++;
        if (order === array.length) {
          resolve(result);
        }
      };
      array.forEach((current, index) => {
        if (current instanceof MyPromise) {
          current.then(
            value => addData(index, value),
            reason => addData(index, reason)
          );
        } else {
          addData(index, current);
        }
      });
    });
  }
  static any(array) {
    let result = [];
    let order = 0;
    return new MyPromise((resolve, reject) => {
      function addData(key, value) {
        result[key] = value;
        order++;
        if (order === array.length) {
          reject(result);
        }
      }
      array.forEach((current, index) => {
        if (current instanceof MyPromise) {
          current.then(
            value => resolve(value),
            reason => addData(index, reason)
          );
        } else {
          resolve(value);
        }
      });
    });
  }
  static resolve(value) {
    if (value instanceof MyPromise) return value;
    return new MyPromise(resolve => resolve(value));
  }
  static reject(value) {
    return new MyPromise((resolve, reject) => reject(value));
  }
  static try(fn) {
    return new MyPromise(resolve => resolve(fn()));
  }
}

function resolvePromise(promiseThen, value, resolve, reject) {
  if (promiseThen === value) {
    return reject(
      new TypeError('Chaining cycle detected for promise #<Promise>')
    );
  }
  if (value instanceof MyPromise) {
    value.then(resolve, reject);
  } else {
    resolve(value);
  }
}
